package config

import (
	"gitee.com/dennis-kk/service-box-go/util/config/loader"
	"gitee.com/dennis-kk/service-box-go/util/config/loader/memory"
	"gitee.com/dennis-kk/service-box-go/util/config/reader"
	"gitee.com/dennis-kk/service-box-go/util/config/reader/yaml"
	"gitee.com/dennis-kk/service-box-go/util/config/source"
	"sync"
)

type config struct {
	sync.RWMutex
	opts Options
	// the current snapshot
	snap *loader.Snapshot
	// the current values
	values reader.Values
}

func newConfig(opts ...Option) (Config, error) {
	var c config

	err := c.Init(opts...)
	if err != nil {
		return nil, err
	}

	return &c, nil
}

func (c *config) Init(opts ...Option) error {
	c.opts = Options{
		Reader: yaml.NewReader(),
	}
	for _, o := range opts {
		o(&c.opts)
	}

	// default loader uses the configured reader
	if c.opts.Loader == nil {
		c.opts.Loader = memory.NewLoader(loader.WithReader(c.opts.Reader))
	}

	err := c.opts.Loader.Load(c.opts.Source...)
	if err != nil {
		return err
	}

	c.snap, err = c.opts.Loader.Snapshot()
	if err != nil {
		return err
	}

	c.values, err = c.opts.Reader.Values(c.snap.ChangeSet)
	if err != nil {
		return err
	}

	return nil
}

func (c *config) Options() *Options {
	return &(c.opts)
}

func (c *config) Map() map[string]interface{} {
	c.RLock()
	defer c.RUnlock()
	return c.values.Map()
}

func (c *config) Scan(v interface{}) error {
	c.RLock()
	defer c.RUnlock()
	return c.values.Scan(v)
}

func (c *config) Close() error {
	return nil
}

func (c *config) Get(path ...string) reader.Value {
	c.RLock()
	defer c.RUnlock()

	if c.values != nil {
		return c.values.Get(path...)
	}
	// no value
	return newValue()
}

func (c *config) Bytes() []byte {
	c.RLock()
	defer c.RUnlock()

	if c.values == nil {
		return []byte{}
	}

	return c.values.Bytes()
}

func (c *config) Load(sources ...source.Source) error {
	if err := c.opts.Loader.Load(sources...); err != nil {
		return err
	}

	snap, err := c.opts.Loader.Snapshot()
	if err != nil {
		return err
	}

	c.Lock()
	defer c.Unlock()

	c.snap = snap
	vals, err := c.opts.Reader.Values(snap.ChangeSet)
	if err != nil {
		return err
	}
	c.values = vals

	return nil
}

func (c *config) String() string {
	return "config"
}
